package es.ugr.beequizz.dao.impl;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import es.ugr.beequizz.bean.ClasificacionBean;
import es.ugr.beequizz.bean.InformacionPartidaBean;
import es.ugr.beequizz.bean.MisPartidasBean;
import es.ugr.beequizz.bean.PartidaBean;
import es.ugr.beequizz.bean.PuntuacionBean;
import es.ugr.beequizz.dao.GenericDAO;
import es.ugr.beequizz.dao.PartidaDAO;

@Repository
public class PartidaDAOImpl extends GenericDAO implements PartidaDAO{

	/*Requete SQL para introducir los datos en la tabla partida*/

	private static final String NUEVA_PARTIDA_SQL = "INSERT INTO partida ( usuario_id, juego_id, fecha, partida_acabada, turno)" +
			"VALUES ( :usuarioId, :juegoId, :fecha, false, :usuarioId) RETURNING id"; 

	private static final String PUNTUACION_FINAL_SQL = "INSERT INTO puntuacion (partida_id, usuario_id,puntuacion) VALUES ( :partidaId, :usuarioId, :puntuacionTotal)";

	private static final String PARTIDA_MULTIJUGADOR_SQL = "INSERT INTO partida_usuario  (partida_id, usuario_id, siguiente_id, puntuacion)" +
			"VALUES ( :idPartida, :idJugador, :idSiguiente, 0)"; 

	private static final String PREGUNTA_PARTIDA_SQL = "INSERT INTO preguntas_partida (partida_id, pregunta_id)" +
			"VALUES ( :partidaId, :preguntaId)";

	/*Demanda para cambiar el campo partida acabada en true*/
	private static final String PARTIDA_TERMINADA_SQL = "UPDATE partida SET partida_acabada=true WHERE id = :idPartida";

	/*Demanda para recuperar una partida*/
	private static final String RECUPERAR_PARTIDA_SQL = "SELECT usuario_id, juego_id, id FROM partida WHERE usuario_id = :usuarioId AND partida_acabada = false";


	/*Demanda para inserta una pregunta en la tabla preguntas_partida*/
	private static final String INSERTAR_PREGUNTA_SQL = "INSERT INTO preguntas_partida (partida_id, pregunta_id, usuario_id)" +
			"VALUES ( :partidaId, :preguntaId, :usuarioId)";

	/*Demanda para recuperar la puntuacion de un usuario en la partida*/
	private static final String RECUPERAR_PUNTUACION_SQL = "SELECT puntuacion FROM partida_usuario WHERE usuario_id = :usuarioId AND partida_id = :partidaId";

	private static final String RECUPERAR_PUNTUACION_GLOBAL_SQL = "SELECT puntuacion, usuario.apodo FROM partida_usuario " +
			" INNER JOIN usuario ON (usuario.id = partida_usuario.usuario_id) " +
			" WHERE partida_id = :idPartida";


	/*Demanda para registrar la puntuacion de un usuario cuando la partida es multijugadores*/
	private static final String PUNTUACION_SQL = "UPDATE partida_usuario SET puntuacion = :puntuacion WHERE usuario_id = :usuarioId AND partida_id = :partidaId";

	/*Demanda para recuperar el estado de la partida*/
	private static final String ESTADO_PARTIDA_SQL = "SELECT partida_acabada FROM partida WHERE id = :partidaId";

	/*Demanda para recuperar las partidas en curso */
	private static final String PARTIDAS_EN_CURSO_SQL = "SELECT partida.id, usuario.apodo, fecha, tema_id, dificultad_id FROM partida " + 
			"INNER JOIN partida_usuario ON (partida.id = partida_usuario.partida_id  AND partida_usuario.usuario_id = :idUsuario)" +
			"INNER JOIN usuario ON (usuario.id = partida.usuario_id) " +
			"WHERE partida_acabada = FALSE AND juego_id = :idJuego ";

	private static final String RECUPERAR_INFORMACION_PARTIDA_SQL = "SELECT partida.id, usuario.apodo, fecha, tema_id, dificultad_id FROM partida " + 
			"INNER JOIN usuario ON (usuario.id = partida.usuario_id) " +
			"WHERE partida.id = :idPartida ";

	private static final String NUEVA_PARTIDA_COMPLETA_SQL = "INSERT INTO partida ( usuario_id, juego_id, tema_id, dificultad_id, fecha, partida_acabada, turno) " +
			"VALUES ( :usuarioId, :juegoId, :tema, :dificultad, :fecha, false, :usuarioId) RETURNING id";

	private static final String RECUPERAR_PARTICIPANTES_SQL = "SELECT apodo FROM partida_usuario " +
			" INNER JOIN usuario ON (usuario.id = partida_usuario.usuario_id) " +
			" WHERE partida_usuario.partida_id = :idPartida ";

	private static final String RECUPERAR_RESULTADOS_MIS_PARTIDAS = "SELECT part.id, part.fecha, juego.nombre AS juegoNombre, asignatura.nombre AS asignatura, dificultad.label AS dificultad, part_usua.puntuacion " +
			" FROM partida part " +
			" INNER JOIN partida_usuario part_usua ON (part_usua.partida_id = part.id) " +
			" INNER JOIN usuario ON (usuario.id = part_usua.usuario_id) " +
			" INNER JOIN juego ON (juego.id = part.juego_id) " +
			" LEFT OUTER JOIN dificultad ON (dificultad.id = part.dificultad_id) " +
			" LEFT OUTER JOIN asignatura ON (asignatura.id = part.tema_id) " +
			" WHERE partida_acabada = TRUE AND part_usua.usuario_id = :jugadorId" +
			" ORDER BY part.fecha DESC";

	private static final String RECUPERAR_CLASIFICACION = "SELECT usuario.nombre, count(*) AS partidasGanadas" +
			" FROM partida part " +
			" INNER JOIN partida_usuario part_usua ON (part_usua.partida_id = part.id) " +
			" INNER JOIN usuario ON (usuario.id = part_usua.usuario_id) " +
			" WHERE partida_acabada = TRUE  " +
			" AND  part_usua.puntuacion = (SELECT max(puntuacion) FROM partida_usuario WHERE partida_id = part.id) " +
			" AND juego_id = :juegoId " +
			" AND dificultad_id = :dificultadId " +
			" AND tema_id = :asignaturaId " +
			" GROUP BY usuario.nombre " +
			" ORDER BY part.fecha DESC";

	private class PartidaRowMapper implements RowMapper<PartidaBean>{

		public PartidaBean mapRow(ResultSet result, int arg1) throws SQLException{
			PartidaBean bean = new PartidaBean();
			bean.setId(result.getLong("id"));
			bean.setJuego_id(result.getLong("juego_id"));
			bean.setJugador_id(result.getLong("usuario_id"));			
			return bean;
		}

	}
	
	private class ResultadosMisPartidasRowMapper implements RowMapper<MisPartidasBean>{

		public MisPartidasBean mapRow(ResultSet result, int arg1) throws SQLException{
			MisPartidasBean bean = new MisPartidasBean();
			bean.setIdPartida(result.getLong("id"));
			bean.setDificultad(result.getString("dificultad"));
			bean.setAsignatura(result.getString("asignatura"));
			bean.setFecha(result.getTimestamp("fecha"));
			bean.setNombreJuego(result.getString("juegoNombre"));
			bean.setPuntuacion(result.getLong("puntuacion"));
			
			return bean;
		}

	}

	private class ClasificacionRowMapper implements RowMapper<ClasificacionBean>{

		public ClasificacionBean mapRow(ResultSet result, int arg1) throws SQLException{
			ClasificacionBean bean = new ClasificacionBean();
			bean.setNombre(result.getString("nombre"));
			bean.setPartidasGanadas(result.getLong("partidasGanadas"));
			return bean;
		}

	}


	private class InformacionPartidaRowMapper implements RowMapper<InformacionPartidaBean>{
		public InformacionPartidaBean mapRow(ResultSet result, int arg1) throws SQLException{
			InformacionPartidaBean bean = new InformacionPartidaBean();
			bean.setPartidaId(result.getLong("id"));
			bean.setApodo(result.getString("apodo"));
			bean.setFecha(result.getTimestamp("fecha"));
			long temaId = result.getLong("tema_id");
			if (temaId != 0) {
				bean.setTemaId(temaId);
			}
			long dificultadId = result.getLong("dificultad_id");
			if (dificultadId != 0) {
				bean.setDificultadId(dificultadId);
			}
			return bean;

		}
	}

	private class PuntuacionBeanRowMapper implements RowMapper<PuntuacionBean>{
		public PuntuacionBean mapRow(ResultSet result, int arg1) throws SQLException{
			PuntuacionBean bean = new PuntuacionBean();
			bean.setPuntuacion(result.getLong("puntuacion"));
			bean.setApodo(result.getString("apodo"));

			return bean;

		}
	}

	public Long crearPartida(Long usuarioId, Long juegoId) {

		/*Inicializamos los parametros que vamos a guardar*/
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("usuarioId", usuarioId);
		params.put("juegoId", juegoId);
		params.put("fecha", new Date());

		return this.namedTemplate.queryForLong(NUEVA_PARTIDA_SQL, params);

	}

	public Long crearPartida(Long jugadorId, Long juegoId, Long temaId,
			Long dificultadId) {
		/*Inicializamos los parametros que vamos a guardar*/
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("usuarioId", jugadorId);
		params.put("juegoId", juegoId);
		params.put("fecha", new Date());
		params.put("dificultad",dificultadId);
		params.put("tema", temaId);
		return this.namedTemplate.queryForLong(NUEVA_PARTIDA_COMPLETA_SQL, params);

	}

	public void resultadoPartida(Long partidaId, Long usuarioId, Long puntuacionTotal) {

		Map<String, Object> params = new HashMap<String, Object>();
		params.put("partidaId", partidaId);
		params.put("usuarioId", usuarioId);
		params.put("puntuacionTotal", puntuacionTotal);

		this.namedTemplate.update(PUNTUACION_FINAL_SQL, params);
	}

	public void partidaMultijugador(Long idPartida, Long idJugador, Long idSiguiente) {

		Map<String, Object> params = new HashMap<String, Object>();

		params.put("idPartida", idPartida);
		params.put("idJugador", idJugador);
		params.put("idSiguiente", idSiguiente);

		this.namedTemplate.update(PARTIDA_MULTIJUGADOR_SQL, params);
	}

	public void preguntaPartida(Long partidaId, Long id) {

		Map<String, Object> params = new HashMap<String, Object>();

		params.put("partidaId", partidaId);
		params.put("preguntaId", id);

		this.namedTemplate.update(PREGUNTA_PARTIDA_SQL, params);

	}

	public void partidaTerminada(Long idPartida) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("idPartida", idPartida);

		namedTemplate.update(PARTIDA_TERMINADA_SQL, params);
	}

	public List<PartidaBean> recuperarPartidas(Long idUsuario) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("usuarioId", idUsuario);
		return namedTemplate.query(RECUPERAR_PARTIDA_SQL, params, new PartidaRowMapper());
	}

	public void insertarUnaPregunta(Long preguntaId, Long partidaId,
			Long usuarioId) {
		Map<String, Object> params = new HashMap<String, Object>();

		params.put("partidaId", partidaId);
		params.put("preguntaId", preguntaId);
		params.put("usuarioId", usuarioId);

		this.namedTemplate.update(INSERTAR_PREGUNTA_SQL, params);

	}

	public Long recuperarPuntuacion(Long partidaId, Long usuarioId) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("partidaId", partidaId);
		params.put("usuarioId", usuarioId);

		return namedTemplate.queryForObject(RECUPERAR_PUNTUACION_SQL, params, Long.class);
	}

	public void puntuacion(Long partidaId, Long usuarioId, Long puntuacion) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("partidaId", partidaId);
		params.put("usuarioId", usuarioId);
		params.put("puntuacion", puntuacion);

		this.namedTemplate.update(PUNTUACION_SQL, params);

	}

	public Boolean recuperarEstadoPartida(Long partidaId) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("partidaId", partidaId);

		return namedTemplate.queryForObject(ESTADO_PARTIDA_SQL, params, Boolean.class);
	}

	public List<InformacionPartidaBean> recuperarPartidasEnCurso(
			Long idUsuario, Long idJuego) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("idUsuario", idUsuario);
		params.put("idJuego", idJuego);
		return namedTemplate.query(PARTIDAS_EN_CURSO_SQL, params, new InformacionPartidaRowMapper());
	}

	public InformacionPartidaBean recuperarInformacionPartida(Long idPartida) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("idPartida", idPartida);
		return namedTemplate.queryForObject(RECUPERAR_INFORMACION_PARTIDA_SQL, params, new InformacionPartidaRowMapper());
	}

	public List<PuntuacionBean> recuperarPuntuacion(Long partidaId) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("idPartida", partidaId);
		return namedTemplate.query(RECUPERAR_PUNTUACION_GLOBAL_SQL, params, new PuntuacionBeanRowMapper());

	}

	public List<String> recuperarListaParticipantes(Long idPartida) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("idPartida", idPartida);
		return namedTemplate.queryForList(RECUPERAR_PARTICIPANTES_SQL, params, String.class);
	}

	@Override
	public List<MisPartidasBean> recuperarResultadosMisPartidas(Long jugadorId) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("jugadorId", jugadorId);
		return namedTemplate.query(RECUPERAR_RESULTADOS_MIS_PARTIDAS, params, new ResultadosMisPartidasRowMapper());
	}

	@Override
	public List<ClasificacionBean> recuperarClasificacion(Long juegoId,
			Long asignaturaId, Long dificultadId) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("juegoId", juegoId);
		params.put("asignaturaId", asignaturaId);
		params.put("dificultadId", dificultadId);
		return namedTemplate.query(RECUPERAR_CLASIFICACION, params, new ClasificacionRowMapper());
	}

}
